#import "RNListItemCell.h"
#import "RNCompletedCheckboxCell.h"
#import "RNStepperCell.h"
#import "RNConstants.h"
#import "RNListItem.h"
#import "NSBezierPath+MCAdditions.h"
#import "NSShadow+MCAdditions.h"

NSUInteger const RNListItemCellCheckBoxLength = 18;
NSUInteger const RNListItemCellTagPadding = 20;
NSUInteger const RNListItemCellTagElementPadding = 5;
NSUInteger const RNListItemCellTagHeight = 14;
NSUInteger const RNListItemCellTagBezelCornerRadius = 8;
NSUInteger const RNListItemCellUpperShadowHeight = 2;
NSUInteger const RNListItemCellLowerShadowHeight = 1;
static NSUInteger minimumTitleWidth = 75;
static NSUInteger notesPadding = 5;
static CGFloat const maximumTagWidth = 100.0f;

static NSGradient *backgroundGradient, *backgroundGradientSelected, *backgroundGradientSelectedInactive;
static NSColor *backgroundColorCompleted, *backgroundColorCompletedSelected;
static NSColor *backgroundColorCompletedSelectedInactive;
static NSGradient *tagsGradient, *tagsGradientHighlighted, *tagsGradientComplete, *tagsGradientCompleteHighlighted;
static NSGradient *tagsGradientCompleteHighlightedInactive, *tagsGradientHighlightedInactive;
static NSColor *tagsBorderColor, *tagsBorderColorHighlighted, *tagsBorderColorCompleted, *tagsBorderColorCompletedHighlighted, *tagsTextColorHighlighted, *tagsTextColor;
static NSColor *titleTextColor, *titleTextColorComplete;
static NSGradient *completedItemUpperShadow, *completedItemLowerShadow;
static NSShadow *incompleteItemDropShadow;


@implementation RNListItemCell

@synthesize heartCell = heartCell_;
@synthesize notesCell = notesCell_;
@synthesize checkBoxCell = checkBoxCell_;
@synthesize stepperCell = stepperCell_;
@synthesize count = count_;
@synthesize complete = complete_;
@synthesize notes = notes_;
@synthesize tags = tags_;
@synthesize favorite = favorite_;
@synthesize list = list_;
@synthesize tagAttributes = tagAttributes_;
@synthesize shouldDrawUpperShadow = shouldDrawUpperShadow_;
@synthesize shouldDrawLowerShadow = shouldDrawLowerShadow_;

+(void)initialize {
    backgroundGradient = [[NSGradient alloc] initWithStartingColor:[NSColor whiteColor] endingColor:[NSColor colorWithCalibratedWhite:0.918f alpha:1.0f]];
    backgroundGradientSelected = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:0.878f green:0.918f blue:0.988f alpha:1.0f] endingColor:[NSColor colorWithCalibratedRed:0.82f green:0.859f blue:0.929f alpha:1.0f]];
    backgroundGradientSelectedInactive = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.85f alpha:1.000f] endingColor:[NSColor colorWithCalibratedWhite:0.8f alpha:1.000f]];
    backgroundColorCompleted = [NSColor colorWithCalibratedRed:0.718f green:0.730f blue:0.748f alpha:1.000f];
    backgroundColorCompletedSelected = [NSColor colorWithCalibratedRed:0.772f green:0.810f blue:0.864f alpha:1.000f];
    backgroundColorCompletedSelectedInactive = [NSColor colorWithCalibratedWhite:0.8f alpha:1.0f];
    tagsGradient = [[NSGradient alloc] initWithStartingColor:[NSColor whiteColor] endingColor:[NSColor colorWithCalibratedRed:0.818f green:0.825f blue:0.853f alpha:1.000f]];
    tagsGradientHighlighted = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:0.958f green:0.973f blue:0.998f alpha:1.000f] endingColor:[NSColor colorWithCalibratedRed:0.787f green:0.825f blue:0.880f alpha:1.000f]];
    tagsGradientComplete = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.804f alpha:1.000f] endingColor:[NSColor colorWithCalibratedRed:0.701f green:0.703f blue:0.734f alpha:1.000f]];
    tagsGradientCompleteHighlighted = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedRed:0.831f green:0.871f blue:0.929f alpha:1.000f] endingColor:[NSColor colorWithCalibratedRed:0.719f green:0.754f blue:0.804f alpha:1.000f]];
    tagsGradientCompleteHighlightedInactive = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.85f alpha:1.0f] endingColor:[NSColor colorWithCalibratedWhite:0.8f alpha:1.0f]];
    tagsGradientHighlightedInactive = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.95f alpha:1.0f] endingColor:[NSColor colorWithCalibratedWhite:0.85f alpha:1.0f]];
    tagsBorderColorCompleted = [NSColor colorWithCalibratedWhite:0.484f alpha:1.000f];
    tagsBorderColorCompletedHighlighted = tagsBorderColorCompleted;
    tagsBorderColor = [NSColor colorWithCalibratedWhite:0.663f alpha:1.000f];
    tagsTextColorHighlighted = [NSColor colorWithCalibratedRed:0.402f green:0.409f blue:0.460f alpha:1.000f];
    tagsTextColor = [NSColor colorWithCalibratedRed:0.471f green:0.477f blue:0.520f alpha:1.000f];
    titleTextColor = [NSColor blackColor];
    titleTextColorComplete = [NSColor colorWithCalibratedWhite:0.35f alpha:1.000f];
    completedItemUpperShadow = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.0f] endingColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.3f]];
    completedItemLowerShadow = [[NSGradient alloc] initWithStartingColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.0f] endingColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.1f]];
    incompleteItemDropShadow = [[NSShadow alloc] initWithColor:[NSColor colorWithCalibratedWhite:0.0f alpha:0.5f] offset:NSMakeSize(0, -1) blurRadius:3.0f];
}

+ (BOOL)prefersTrackingUntilMouseUp {
    return YES;
}

-(id)initTextCell:(NSString *)theString {
    if (!(self = [super initTextCell:theString]))
        return nil;
        
    [self setLineBreakMode:NSLineBreakByTruncatingTail];
    [self setFont:[NSFont systemFontOfSize:12.0f]];
    
    RNCompletedCheckboxCell *checkBox = [[RNCompletedCheckboxCell alloc] initImageCell:nil];
    [checkBox setType:NSToggleButton];
    [checkBox setImagePosition:NSImageOnly];
    [checkBox setBezelStyle:NSRegularSquareBezelStyle];
    [self setCheckBoxCell:checkBox];

    [self setStepperCell:[[RNStepperCell alloc] initTextCell:@""]];
    [[self stepperCell] setSendsActionOnEndEditing:YES];
    [[self stepperCell] setFontSize:11.0f];
    [[self stepperCell] setTextColor:[NSColor colorWithCalibratedRed:0.402f green:0.410f blue:0.467f alpha:1.000f]];
    [[self stepperCell] setArrowColor:[NSColor colorWithCalibratedRed:0.500f green:0.510f blue:0.582f alpha:1.000f]];
    [[self stepperCell] setArrowPressedColor:[NSColor colorWithCalibratedRed:0.316f green:0.331f blue:0.375f alpha:1.000f]];
    
    NSButtonCell *heartButtonCell = [[NSButtonCell alloc] initImageCell:[NSImage imageNamed:@"sourceList_favorites.png"]];
    [heartButtonCell setBordered:NO];
    [heartButtonCell setImageScaling:NSImageScaleProportionallyDown];
    [self setHeartCell:heartButtonCell];
    
    NSButtonCell *notesButtonCell = [[NSButtonCell alloc] initImageCell:[NSImage imageNamed:@"Edit-Note.png"]];
    [notesButtonCell setBordered:NO];
    [notesButtonCell setImageScaling:NSImageScaleProportionallyDown];
    [self setNotesCell:notesButtonCell];

    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
    NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    [paragraphStyle setAlignment:NSCenterTextAlignment];
    [paragraphStyle setLineBreakMode:NSLineBreakByTruncatingTail];
    [attributes setValue:paragraphStyle forKey:NSParagraphStyleAttributeName];
    [attributes setValue:[NSFont controlContentFontOfSize:9.0f] forKey:NSFontAttributeName];
    [self setTagAttributes:attributes];
    
    return self;
}

-(id)copyWithZone:(NSZone *)zone {
    RNListItemCell *copy = [super copyWithZone:zone];
    
    copy->checkBoxCell_ = nil;
    [copy setCheckBoxCell:[[self checkBoxCell] copy]];

    copy->stepperCell_ = nil;
    [copy setStepperCell:[[self stepperCell] copy]];
    
    copy->heartCell_ = nil;
    [copy setHeartCell:[[self heartCell] copy]];
    
    copy->notesCell_ = nil;
    [copy setNotesCell:[[self notesCell] copy]];
    
    return copy;
}

-(NSArray *)keys {
    return [NSArray arrayWithObjects:@"title", @"count", @"complete", @"notes", @"tags", nil];
}

- (id)objectValue {
    return [self dictionaryWithValuesForKeys:[self keys]];
}

-(void)setObjectValue:(id)obj {
    if (![obj isKindOfClass:[NSDictionary class]])
        return;
    [self setValuesForKeysWithDictionary:obj];
    [super setObjectValue:[obj valueForKey:RNTitleKey]];
}


#pragma mark -
#pragma mark Cell Behavior

-(BOOL)isSelectable {
    return YES;
}

-(BOOL)isEditable {
	return YES;
}


#pragma mark -
#pragma mark Editing

-(void)editWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject event:(NSEvent *)theEvent {
	[self endEditing:textObj];
    [NSApp sendAction:@selector(editSelectedItem:) to:nil from:self];    
}

-(void)selectWithFrame:(NSRect)aRect inView:(NSView *)controlView editor:(NSText *)textObj delegate:(id)anObject start:(NSInteger)selStart length:(NSInteger)selLength {
	[self endEditing:textObj];
    [NSApp sendAction:@selector(editSelectedItem:) to:nil from:self];    
}


#pragma mark -
#pragma mark Drawing

-(void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
    NSRect insetFrame = NSInsetRect(cellFrame, RNDetailViewBorderPadding, 0.0f);
    NSBezierPath *boundingPath = [NSBezierPath bezierPathWithRoundedRect:insetFrame xRadius:RNDetailViewCellCornerRadius yRadius:RNDetailViewCellCornerRadius];

    // Draw background
    if ([[self complete] boolValue]) {
        NSColor *highlightedColor = [[controlView window] firstResponder] == controlView && [NSApp isActive] ? backgroundColorCompletedSelected : backgroundColorCompletedSelectedInactive;
        NSColor *backgroundColor = [self isHighlighted] ? highlightedColor : backgroundColorCompleted;
        [backgroundColor setFill];
        [boundingPath fill];
    }
    else {
        [NSGraphicsContext saveGraphicsState];
        [incompleteItemDropShadow set];
        NSGradient *highlightedGradient = [[controlView window] firstResponder] == controlView && [NSApp isActive] ? backgroundGradientSelected : backgroundGradientSelectedInactive;
        NSGradient *gradient = [self isHighlighted] ? highlightedGradient : backgroundGradient;
        [[NSColor blackColor] setFill];
        [boundingPath fill];
        [gradient drawInBezierPath:boundingPath angle:90.0f];
        [NSGraphicsContext restoreGraphicsState];
    }
    
    // Draw check box
    [[self checkBoxCell] setState:([[self complete] boolValue] ? NSOnState : NSOffState)];
    [[self checkBoxCell] drawWithFrame:[self checkBoxRectForBounds:cellFrame] inView:controlView];
    
    // Draw stepper
    [[self stepperCell] setIntegerValue:[[self count] integerValue]];
    [[self stepperCell] drawWithFrame:[self stepperRectForBounds:cellFrame] inView:controlView];
    
    // Draw tags
    [self drawTagsWithFrame:[self tagsRectForBounds:cellFrame] inView:controlView];
    
    // Draw favorite icon
    if ([[self favorite] boolValue]) {
        [[self heartCell] drawWithFrame:[self favoriteRectForBounds:cellFrame] inView:controlView];
    }
    
    // Draw notes icon
    if ([self notes]) {
        [[self notesCell] drawWithFrame:[self notesRectForBounds:cellFrame] inView:controlView];
    }
    
    // Draw Text
    NSColor *titleColor = [[self complete] boolValue] ? titleTextColorComplete : titleTextColor;
    NSMutableAttributedString *titleString = [[self attributedStringValue] mutableCopy];
    [titleString addAttribute:NSForegroundColorAttributeName value:titleColor range:NSMakeRange(0, [titleString length])];
    [titleString drawInRect:[self titleRectForBounds:cellFrame]];
    
    if (![[self complete] boolValue])
        return;
    
    [NSGraphicsContext saveGraphicsState];
    [boundingPath setClip];

    if ([self shouldDrawUpperShadow]) {
        NSRect upperShadowRect = insetFrame;
        upperShadowRect.size.height = RNListItemCellUpperShadowHeight;
        [completedItemUpperShadow drawInRect:upperShadowRect angle:-90];
    }
    if ([self shouldDrawLowerShadow]) {
        NSRect lowerShadowRect = insetFrame;
        lowerShadowRect.size.height = RNListItemCellLowerShadowHeight;
        lowerShadowRect.origin.y = NSMaxY(insetFrame) - RNListItemCellLowerShadowHeight;
        [completedItemLowerShadow drawInRect:lowerShadowRect angle:90];
    }
    [NSGraphicsContext restoreGraphicsState];
}

-(void)drawTagsWithFrame:(NSRect)tagRect inView:(NSView *)controlView {
    if (![self tags])
        return;
    NSColor *textColor = [self isHighlighted] ? tagsTextColorHighlighted : tagsTextColor;
    NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithDictionary:[self tagAttributes]];
    [attributes setValue:textColor forKey:NSForegroundColorAttributeName];

    NSRect bezelRect = tagRect;
    bezelRect.origin.x = NSMaxX(tagRect);
    NSGradient *currentGradient = nil;
    if ([[self complete] boolValue]) {
        NSGradient *highlightedGradient = [[controlView window] firstResponder] == controlView && [NSApp isActive] ? tagsGradientCompleteHighlighted : tagsGradientCompleteHighlightedInactive;
        currentGradient = [self isHighlighted] ? highlightedGradient : tagsGradientComplete;
    }
    else {
        NSGradient *highlightedGradient = [[controlView window] firstResponder] == controlView && [NSApp isActive] ? tagsGradientHighlighted : tagsGradientHighlightedInactive;
        currentGradient = [self isHighlighted] ? highlightedGradient : tagsGradient;
    }
    NSColor *borderColor = nil;
    if ([[self complete] boolValue])
        borderColor = [self isHighlighted] ? tagsBorderColorCompletedHighlighted : tagsBorderColorCompleted;
    else
        borderColor = [self isHighlighted] ? tagsBorderColorHighlighted : tagsBorderColor;

    for (NSString *tag in [[self tags] reverseObjectEnumerator]) {
        NSAttributedString *attributedTag = [[NSAttributedString alloc] initWithString:tag attributes:attributes];
        CGFloat tagWidth = [attributedTag size].width + RNListItemCellTagPadding;
        bezelRect.origin.x -= tagWidth;
        if (NSMinX(bezelRect) < NSMinX(tagRect))
            return;
        bezelRect.size.width = tagWidth;
        NSBezierPath *bezelPath = [NSBezierPath bezierPathWithRoundedRect:bezelRect xRadius:RNListItemCellTagBezelCornerRadius yRadius:RNListItemCellTagBezelCornerRadius];
        [currentGradient drawInBezierPath:bezelPath angle:90];
        [borderColor setStroke];
        [bezelPath stroke];
        NSRect tagTextRect = NSInsetRect(bezelRect, RNListItemCellTagPadding / 2, 1);
        [attributedTag drawInRect:tagTextRect];
        bezelRect.origin.x -= RNListItemCellTagElementPadding;
    }
}

-(NSRect)checkBoxRectForBounds:(NSRect)cellFrame {
    NSRect checkBoxFrame = cellFrame;
    checkBoxFrame.origin.x += RNGenericCellEndPadding;
    checkBoxFrame.size.width = RNListItemCellCheckBoxLength;
    checkBoxFrame.size.height = RNListItemCellCheckBoxLength;
    checkBoxFrame.origin.y += NSMidY(cellFrame) - NSMidY(checkBoxFrame) - 1;
    return checkBoxFrame;
}

-(NSRect)stepperRectForBounds:(NSRect)cellFrame {
    NSRect stepperFrame = cellFrame;
    stepperFrame.origin.x = NSMaxX([self checkBoxRectForBounds:cellFrame]) + RNGenericCellElementPadding;
    stepperFrame.size.width = RNStepperCellWidth;
    stepperFrame.size.height = RNStepperCellHeight;
    stepperFrame.origin.y += floor(NSMidY(cellFrame) - NSMidY(stepperFrame));
    return stepperFrame;
}

-(CGFloat)tagsWidth {
    CGFloat totalWidth = 0;
    for (NSString *tag in [self tags]) {
        NSAttributedString *tagString = [[NSAttributedString alloc] initWithString:tag attributes:[self tagAttributes]];
        totalWidth += fmin([tagString size].width + RNListItemCellTagPadding, maximumTagWidth);
    }
    totalWidth += ([[self tags] count] - 1) * RNListItemCellTagElementPadding;
    return totalWidth;
}

-(NSRect)titleRectForBounds:(NSRect)cellFrame {
    NSRect titleFrame = cellFrame;
    titleFrame.origin.x = NSMaxX([self stepperRectForBounds:cellFrame]) + RNGenericCellElementPadding;
    CGFloat titleWidth = [[self attributedStringValue] size].width;
    CGFloat maxTitleWidth = NSMaxX(cellFrame) - RNGenericCellEndPadding - titleFrame.origin.x;
    
    if ([self tags] && [[self tags] count] > 0)
        maxTitleWidth -= [self tagsWidth] + RNGenericCellElementPadding;
    if ([[self favorite] boolValue])
        maxTitleWidth -= RNDetailCellFavoriteWidth + RNGenericCellElementPadding;
    if ([self notes])
        maxTitleWidth -= notesPadding + RNDetailCellNotesLength;
    NSSize titleSize = [[self attributedStringValue] size];
    CGFloat titleWidthLimit = fmin(titleSize.width, minimumTitleWidth);
    maxTitleWidth = fmax(maxTitleWidth, titleWidthLimit); 
    titleFrame.size.width = fmin(titleWidth, maxTitleWidth);
    titleFrame.size.height = titleSize.height;
    titleFrame.origin.y += NSMidY(cellFrame) - NSMidY(titleFrame);
    return titleFrame;
}

-(NSRect)favoriteRectForBounds:(NSRect)cellFrame {
    if (![[self favorite] boolValue])
        return NSZeroRect;
    return NSMakeRect((NSMaxX(cellFrame) - RNDetailCellFavoriteWidth - RNGenericCellEndPadding), NSMinY(cellFrame), RNDetailCellFavoriteWidth, NSHeight(cellFrame));
}

-(NSRect)notesRectForBounds:(NSRect)cellFrame {
    if (![self notes])
        return NSZeroRect;
    
    NSRect titleRect = [self titleRectForBounds:cellFrame];
    NSRect notesRect = NSMakeRect(NSMaxX(titleRect) + notesPadding, NSMinY(cellFrame), RNDetailCellNotesLength, RNDetailCellNotesLength);
    notesRect.origin.y = NSMidY(cellFrame) - (RNDetailCellNotesLength / 2);
    return notesRect;
}

-(NSRect)tagsRectForBounds:(NSRect)cellFrame {
    if (![self tags])
        return NSZeroRect;
    NSRect titleRect = [self titleRectForBounds:cellFrame];
    CGFloat availableWidth = NSMaxX(cellFrame) - RNGenericCellEndPadding - NSMaxX(titleRect) - RNGenericCellElementPadding;
    CGFloat originX = NSMaxX(titleRect) + RNGenericCellElementPadding;
    if ([self notes]) {
        availableWidth -= notesPadding + RNDetailCellNotesLength;
        originX += notesPadding + RNDetailCellNotesLength;
    }
    if ([[self favorite] boolValue]) 
        availableWidth -= RNDetailCellFavoriteWidth + RNGenericCellElementPadding;
    CGFloat originY = NSMinY(cellFrame) + (NSHeight(cellFrame) - RNListItemCellTagHeight) / 2;
    return NSMakeRect(originX, originY, availableWidth, RNListItemCellTagHeight);
}

- (NSColor *)highlightColorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
    return nil;
}


#pragma mark -
#pragma mark Event-handling

- (NSUInteger)hitTestForEvent:(NSEvent *)event inRect:(NSRect)cellFrame ofView:(NSView *)controlView {
    NSPoint eventPoint = [controlView convertPoint:[event locationInWindow] fromView:nil];
    
    NSRect checkBoxFrame = [self checkBoxRectForBounds:cellFrame];
    if ([controlView mouse:eventPoint inRect:checkBoxFrame]) 
        return NSCellHitContentArea | NSCellHitTrackableArea;

    NSRect stepperFrame = [self stepperRectForBounds:cellFrame];
    if ([controlView mouse:eventPoint inRect:stepperFrame]) 
        return NSCellHitContentArea | NSCellHitTrackableArea;
    
    if ([event clickCount] == 1)
        return NSCellHitNone;
    
    return NSCellHitContentArea | NSCellHitEditableTextArea;
}

- (BOOL)trackMouse:(NSEvent *)theEvent inRect:(NSRect)cellFrame ofView:(NSView *)controlView untilMouseUp:(BOOL)untilMouseUp {
    NSPoint eventPoint = [controlView convertPoint:[theEvent locationInWindow] fromView:nil];
    NSRect checkBoxFrame = [self checkBoxRectForBounds:cellFrame];
    BOOL returnValue = NO;
    if ([controlView mouse:eventPoint inRect:checkBoxFrame]) {
        [[self checkBoxCell] setState:([[self complete] boolValue] ? NSOnState : NSOffState)];
        returnValue = [[self checkBoxCell] trackMouse:theEvent inRect:checkBoxFrame ofView:controlView untilMouseUp:YES];
        NSNumber *newState = [NSNumber numberWithBool:([[self checkBoxCell] state] == NSOnState ? YES : NO)];
        [[self representedObject] setComplete:newState];
        [[NSNotificationCenter defaultCenter] postNotificationName:RNListItemCompletionChangedNotification object:[self representedObject]];
        return returnValue;
    }
    
    NSRect stepperFrame = [self stepperRectForBounds:cellFrame];
    if ([controlView mouse:eventPoint inRect:stepperFrame]) {
        [[self stepperCell] setIntegerValue:[[self count] integerValue]];
        returnValue = [[self stepperCell] trackMouse:theEvent inRect:stepperFrame ofView:controlView untilMouseUp:YES];
        NSNumber *newCount = [NSNumber numberWithInteger:[[self stepperCell] integerValue]];
        [(RNListItem *)[self representedObject] setCount:newCount];
        return returnValue;
    }
    return YES;    
}

@end
